package cn.lyf.currey;

import java.lang.reflect.*;
import java.util.function.*;
import java.util.*;
import static cn.lyf.currey.Currying.*;

public class Utils {
    public static MATCH ANY = MATCH.ANY;
    public static MATCH NOTNULL = MATCH.NOTNULL;

    // 常数函数
    public static <R> Function<?, R> constant(R val) {
        return a -> val;
    }

    // 判断是否继承
    public static boolean isExtends(Object father, Object son) {
        if (son == null)
            return true;
        if (father == null)
            return false;
        Class<?> f = father instanceof Class ? (Class<?>) father : father.getClass();
        Class<?> s = son instanceof Class ? (Class<?>) son : son.getClass();
        return f.isAssignableFrom(s);
    }

    // 语义化的匹配在enumMatch中 ANY匹配任何, NOTNULL匹配非空变量
    enum MATCH {
        ANY, NOTNULL
    }

    // Try catch 模块,懒加载
    public static class Try<R> implements Lazy<Optional<R>> {
        public static class NotExistErrorConsumer extends RuntimeException {

        }

        final private Supplier<? extends R> supplier;
        private List<Predicate<Exception>> errorConsumers = new ArrayList<>();
        private Consumer<? super R> successConsumer;

        private Try(Supplier<? extends R> supplier) {
            this.supplier = supplier;
        }

        public static <T> Try<T> of(Supplier<? extends T> supplier) {
            return new Try<T>(supplier);
        }

        public static Try<Object> of(final Runnable runnable) {
            return new Try<Object>(() -> {
                runnable.run();
                return null;
            });
        }

        // 添加一个捕获器，用来捕获异常
        public Try<R> error(Consumer<Exception> consumer) {
            Objects.requireNonNull(consumer, "consumer is null");
            try {
                Class<?> clazz = getInterfaceGenericType(consumer, 0, 0);
                innerError(consumer, clazz);
            } catch (Exception err) {
                errorConsumers.add(e -> {
                    consumer.accept(e);
                    return true;
                });
            }
            return this;
        }

        public Try<R> error(Class<? extends Exception> type, Consumer<Exception> consumer) {
            Objects.requireNonNull(consumer, "consumer is null");
            Objects.requireNonNull(type, "Class<? extends Exception> type is null");
            innerError(consumer, type);
            return this;
        }

        public Try<R> error(Consumer<Exception> consumer, Class<? extends Exception> type) {
            Objects.requireNonNull(consumer, "consumer is null");
            Objects.requireNonNull(type, "Class<? extends Exception> type is null");
            innerError(consumer, type);
            return this;
        }

        private void innerError(Consumer<Exception> consumer, Class<?> type) {
            errorConsumers.add(e -> {
                if (type.isInstance(e)) {
                    consumer.accept(e);
                    return true;
                }
                return false;
            });
        }

        // 成功时调用consumer
        public Try<R> success(Consumer<? super R> consumer) {
            successConsumer = consumer;
            return this;
        }

        @Override
        public Optional<R> get() {
            R result = null;
            try {
                result = supplier.get();
                if (successConsumer == null)
                    return Optional.ofNullable(result);
                successConsumer.accept(result);
            } catch (Exception e) {
                if (errorConsumers.size() == 0)
                    throw new NotExistErrorConsumer();
                boolean catched = false;
                for (int i = 0; i < errorConsumers.size(); i++) {
                    catched = catched || errorConsumers.get(i).test(e);
                }
                if (!catched)
                    throw e;
            }
            return Optional.ofNullable(result);
        }

    }

    // 枚举匹配，不是单纯比较是否相等，详见MATCH枚举类
    public static boolean enumMatch(Object expect, Object actual) {
        if (expect instanceof MATCH) {
            switch ((MATCH) expect) {
                case ANY:
                    return true;
                case NOTNULL:
                    return actual != null;
                default:
                    return false;
            }
        }
        return isEqual(actual, expect);
    }

    // 获取泛型父类实际类型
    public static Class<?> getClassGenericType(Object a, int i) {
        return (Class<?>) ((ParameterizedType) a.getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    // 获取泛型接口实际类型
    public static Class<?> getInterfaceGenericType(Object a, int n, int i) {
        Type[] types = a.getClass().getGenericInterfaces();
        ParameterizedType parameterized = (ParameterizedType) types[n];
        return (Class<?>) parameterized.getActualTypeArguments()[i];
    }

    // 判断是否是数组
    public static boolean isArray(Object o) {
        return o != null && o.getClass().isArray();
    }

    public static boolean isList(Object o) {
        return o != null && (o instanceof List);
    }

    // 增强equal, List和原生数组只要内容一致也可以相等
    public static boolean isEqual(Object o1, Object o2) {
        if (o1 == o2)
            return true;
        int a = 0;
        a += isArray(o1) ? 1 : 0;
        a += isArray(o2) ? 2 : 0;
        a += isList(o1) ? 4 : 0;
        a += isList(o2) ? 8 : 0;
        switch (a) {
            case 3:// a a
                return arrayIsEq(o1, o2);
            case 6:// l a
                Object t = o1;
                o1 = o2;
                o2 = t;
            case 9:// a l
                return arrayEqList(o1, (List<?>) o2);
            default:
                return Objects.equals(o1, o2);
        }
    }

    // 比较数组和List
    private static boolean arrayEqList(Object arr, List<?> list) {
        int i = Array.getLength(arr), j = list.size();
        if (i != j)
            return false;
        Iterator<?> it = list.iterator();
        for (i = 0; i < j; i++) {
            if (isEqual(Array.get(arr, i), it.next()))
                continue;
            return false;
        }
        return true;
    }

    // 比较两个原生数组
    private static boolean arrayIsEq(Object arr0, Object arr1) {
        int i = Array.getLength(arr0), j = Array.getLength(arr1);
        if (i != j)
            return false;
        for (i = 0; i < j; i++) {
            if (isEqual(Array.get(arr0, i), Array.get(arr1, i)))
                continue;
            return false;
        }
        return true;
    }

    // 获取按名字获取方法。包括继承的方法
    public static Method[] getPublicMethodsNamed(Class<?> clazz, String name) {
        return Arrays.stream(clazz.getMethods()).filter(a -> a.getName().equals(name)).toArray(Method[]::new);
    }

    // 通过反射找到第一个兼容参数列表的方法，注意Double 和 Integer 类型并不兼容。但基本类型和包装类该方法会自动匹配
    public static Optional<Executable> findFirstByParameterTypes(Executable[] methods, Class<?>[] types) {
        for (Executable method : methods) {
            if (isParameterCompatibility(method.getParameterTypes(), types)) {
                return Optional.of(method);
            }
        }
        return Optional.empty();
    }

    // 从基本类型映射到包装类型
    static Map<Class<?>, Class<?>> autoBoxingMap;

    static {
        // 自动装箱
        Map<Class<?>, Class<?>> m = new HashMap<>();
        m.put(byte.class, Byte.class);
        m.put(short.class, Short.class);
        m.put(long.class, Long.class);
        m.put(int.class, Integer.class);
        m.put(float.class, Float.class);
        m.put(double.class, Double.class);
        m.put(char.class, Character.class);
        m.put(boolean.class, Boolean.class);
        autoBoxingMap = Collections.unmodifiableMap(m);
    }

    // 若是基本类，装换成包装类
    public static Class<?> autoBoxingType(Class<?> t) {
        if (t.isPrimitive())
            return autoBoxingMap.get(t);
        return t;
    }

    // 判断clazz0 是否兼容 clazz1
    public static boolean isCompatible(Class<?> clazz0, Class<?> clazz1) {
        clazz0 = autoBoxingType(clazz0);
        clazz1 = autoBoxingType(clazz1);
        return clazz0.isAssignableFrom(clazz1);
    }

    // 判断参数列表是否兼容
    public static boolean isParameterCompatibility(Class<?>[] types0, Class<?>[] types1) {
        if (types0.length != types1.length)
            return false;
        boolean result = true;
        for (int i = 0; i < types0.length; i++) {
            result = result && isCompatible(types0[i], types1[i]);
        }
        return result;
    }

    // 可以对称比较也可以非对称 ;主要通过更改比较器实现
    // 非对称比较是指一个对象做另一个的模板，匹配一些对象。类似正则表达式匹配子符串
    // 容器比较特殊处理
    public static boolean eqBy(Object o1, Object o2, BiPredicate<Object, Object> cmp) {
        if (o1 instanceof List)
            o1 = ((List<?>) o1).toArray();
        if (o2 instanceof List)
            o2 = ((List<?>) o2).toArray();
        if (isArray(o1) && isArray(o2)) {
            boolean isEq = true;
            int i = Array.getLength(o1), j = Array.getLength(o2);
            if (i != j)
                return false;
            for (i = 0; i < j && isEq; i++)
                isEq = eqBy(Array.get(o1, i), Array.get(o2, i), cmp);
            return isEq;
        }
        if (o1 instanceof Map)
            o1 = ((Map<?, ?>) o1).entrySet();
        if (o2 instanceof Map)
            o2 = ((Map<?, ?>) o2).entrySet();
        if (o1 instanceof Set && o2 instanceof Set)
            return setEq((Set<?>) o1, (Set<?>) o2, cmp);
        return cmp.test(o1, o2);
    }

    // 最好O(n)最坏O(nlog(调和级数)) 约为 O(nlog(ln (n)))
    // 判断集合是否相等
    public static boolean setEq(Set<?> s1, Set<?> s2, final BiPredicate<Object, Object> cmp) {
        if (s1.size() != s2.size())
            return false;
        boolean isEq = true;
        Set<Box> tmp = new HashSet<Box>();
        for (Object o : s1) {
            tmp.add(new Box(o) {
                @Override
                public boolean equals(Object obj) {
                    Object v1 = ((Box) obj).v;
                    return v == v1 ? true : eqBy(v, v1, cmp);
                }
            });
        }
        for (Object o : s2) {
            isEq = isEq && tmp.contains(new Box(o) {
                @Override
                public boolean equals(Object obj) {
                    Object v1 = ((Box) obj).v;
                    return v == v1 ? true : eqBy(v, v1, cmp);
                }
            });
        }
        return isEq;
    }

    private static class Box {
        Object v;

        Box(Object v) {
            this.v = v;
        }
    }
}
